import DifficultyEnum from "./DifficultyEnum";

/**
 * @param {string[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */

const solve = {}

solve.tdDeepCopy = (oldArr) => {
    let res = []
    oldArr.forEach(value => {
        res.push([...value])
    })
    return res
}

solve.blockIndex = (i, j) => {
    return Math.floor(Math.floor(i / 3) * 3) + Math.floor(j / 3)
}

solve.indexBlock = (blockIndex) => {
    return [
        Math.floor(Math.floor(blockIndex / 3) * 3),
        Math.floor(Math.floor(blockIndex % 3) * 3)
    ]
}

solve.convertToOD = (i, j) => {
    return Math.floor(Math.floor(i * 9) + j)
}

/**
 *
 * @param {number[][]}board 数度数组
 * @returns {boolean} 判断数独是否有是合法数独
 */
solve.isValidSudoku = (board) => {

    const row = new Array(9).fill(0)
    const col = new Array(9).fill(0)
    const block = new Array(9).fill(0)

    for (let i = 0; i < 9; ++i) {
        for (let j = 0; j < 9; ++j) {
            const c = board[i][j]
            if (c !== 0) {
                const blockIndex =
                    Math.floor(Math.floor(i / 3) * 3) + Math.floor(j / 3)
                const index =
                    board[i][j] - 1
                if (((row[i] >> index) & 1) || ((col[j] >> index) & 1) || ((block[blockIndex] >> index) & 1)) {
                    return false
                } else {
                    row[i] |= 1 << index
                    col[j] |= 1 << index
                    block[blockIndex] |= 1 << index
                }
            }
        }
    }

    return true
}

/**
 *
 * @param {number[][]} board 数独题目
 * @returns {boolean} 题目能正确解出返回true 反之返回false
 */
solve.solveSudoku = (board) => {

    const row = new Array(9).fill(0)
    const col = new Array(9).fill(0)
    const blo = new Array(9).fill(0)
    const blockIndex = (i, j) => {
        return Math.floor(Math.floor(i / 3) * 3) + Math.floor(j / 3)
    }

    const possible = (i, j) => {
        const temp
            = row[i]
            | col[j]
            | blo[blockIndex(i, j)]
        const t2 = 0b111111111
        return temp ^ t2
    }

    const fill = (flag, i, j, n) => {
        const bit = 1 << n
        if (flag) {
            row[i] |= bit
            col[j] |= bit
            blo[blockIndex(i, j)] |= bit
        } else {
            row[i] ^= bit
            col[j] ^= bit
            blo[blockIndex(i, j)] ^= bit
        }
    }

    const dfs = (board, i, j) => {
        while (board[i][j] !== 0) {
            if (++j >= 9) {
                j = 0
                ++i
            }
            if (i >= 9) {
                return true
            }
        }

        const s = possible(i, j)

        for (let num = 1; num <= 9; ++num) {
            if (((1 << (num - 1)) & s)) {
                board[i][j] = num
                fill(true, i, j, num - 1)
                if (dfs(board, i, j)) {
                    return true
                }
                board[i][j] = 0
                fill(false, i, j, num - 1)
            }
        }

        return false
    }

    for (let i = 0; i < 9; ++i) {
        for (let j = 0; j < 9; ++j) {
            if (board[i][j] !== 0) {
                const ccode = board[i][j] - 1
                row[i] |= 1 << ccode
                col[j] |= 1 << ccode
                blo[blockIndex(i, j)] |= 1 << ccode
            }
        }
    }

    return dfs(board, 0, 0)
};

/**
 *
 * @param num 产生随机函数的范围 [0,num)
 * @returns {any[]}
 */
solve.createRandom = (num = 81) => {//num是需要生成随机长度 默认81
    const randArr = new Array(num).fill(false)
    return randArr
}

/**
 *
 * @param randCount 会产生randCount个不重复随机数
 * @param num 产生随机数的范围 [0,num)
 * @returns {*[]}
 */
solve.createRandomArray = (randCount = 20, num = 81) => {
    if (randCount > num) {
        return []
    }
    const boolArray = solve.createRandom(num)
    let resArr = []
    for (let i = 0; i < randCount; ++i) {
        let randNum
        do {
            randNum = Math.floor(Math.random() * num)
        } while (boolArray[randNum])
        boolArray[randNum] = true
        resArr.push(randNum)
    }
    return resArr
}

/**
 * 将一维数独下标转换为二位下标
 * @param originArr 一维数独下标
 * @param square 正方形的边长
 * @returns {*[]}
 */
solve.splitArr = (originArr, square = 9) => {
    let sa = []
    originArr.forEach(value => {
        sa.push([Math.floor(value / square), Math.floor(value % square)])
    })
    return sa
}

let skipSlice = (arr, start, end, skip, span) => {
    let res = []
    for (let i = start; i < end; i += skip + span) {
        res.push(...arr.slice(i, Math.min(i + span, arr.length)))
    }
    return res
}

/**
 * 初始化一个数独
 * @param sudoku
 * @param difficulty
 * @returns {any[][]}
 */
solve.initSudokuArr = (sudoku = 9, difficulty = DifficultyEnum.EASY) => {
    let originalArr = solve.createRandomArray(sudoku, sudoku)
    for (let i = 0; i < originalArr.length; i++) {
        ++originalArr[i]
    }
    let temp = new Array(sudoku).fill(0).map((value, index) => index)
    let leftTopIndexArr = solve.splitArr(temp, Math.floor(Math.sqrt(sudoku)))

    const sudokuArray = new Array(sudoku)
        .fill(0)
        .map(_ => new Array(sudoku).fill(0));

    leftTopIndexArr.forEach((value, index) => {
        sudokuArray[value[0]][value[1]] = originalArr[index]
    })
    let sudokuTopArr = originalArr.slice(3)
    sudokuTopArr.sort(() => 0.5 - Math.random())
    for (let i = 3; i < 9; ++i) {
        sudokuArray[0][i] = sudokuTopArr[i - 3]
    }
    let sudokuLeftArr
        = skipSlice(originalArr, 1, originalArr.length, 1, 2)
    for (let j = 3; j < 9; ++j) {
        sudokuArray[j][0] = sudokuLeftArr[j - 3]
    }

    solve.solveSudoku(sudokuArray)

    const subNums = solve.computeDifficulty(difficulty)

    let randIndexArr = solve.createRandomArray(subNums)
    let tdRandIndex = solve.splitArr(randIndexArr)
    tdRandIndex.forEach(value => {
        sudokuArray[value[0]][value[1]] = 0
    })
    return sudokuArray
}
/**
 * 数独难度
 * @param difficulty
 * @returns {number}
 */
solve.computeDifficulty = (difficulty) => {

    switch (difficulty) {
        case DifficultyEnum.EASY:
            return 27
        case DifficultyEnum.MEDIUM:
            return 40
        case DifficultyEnum.HARD:
            return 54
        case DifficultyEnum.GREAT_MASTER:
            return 64
        default:
            return 27
    }
}
/**
 *
 * @param {number[][]} border
 */
solve.isCompleteSudoku = (border) => {
    let numOfZero = 0
    for (let i = 0; i < 9; ++i) {
        for (let j = 0; j < 9; ++j) {
            if (border[i][j] === 0) {
                numOfZero += 1
            }
        }
    }
    return numOfZero === 0
}

const formatNumber = n => {
    n = n.toString()
    return n[1] ? n : `0${n}`
}

solve.dataFormat = (seconds) => {
    var min = Math.floor(seconds / 60)
    var sec = Math.floor(seconds % 60)

    return formatNumber(min) + ':' + formatNumber(sec)
}
export default solve